﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml;
using Mbs.Collections;
using Mbs.Configuration;
using Mbs.Log;
using Mbs.Reflection;
using System.Reflection;

namespace Mbs.Modularity.AddIns
{
    [Serializable]
    [DebuggerDisplay("Id={Id},Description={Description},HideInManager={HideInManager}")]
    internal partial class AddIn : IAddIn
    {
        private IDictionary<string, IAddInItemPath> paths = new Dictionary<string, IAddInItemPath>();
        private static ILog logger = LoggerManager.GetLogger(typeof(AddIn));

        public string Name
        {
            get { return Properties.Get<string>(MbsEnvironment.AddIn.Name); }
        }

        public string Author
        {
            get { return Properties.Get<string>(MbsEnvironment.AddIn.Author); }
        }
        public string Copyright
        {
            get { return Properties.Get<string>(MbsEnvironment.AddIn.Copyright); }
        }
        public string Description
        {
            get { return Properties.Get<string>(MbsEnvironment.AddIn.Description); }
        }
        public string Url
        {
            get { return Properties.Get<string>(MbsEnvironment.AddIn.Url); }
        }

        public string[] Roles { get; private set; }
        public bool HideInManager { get; private set; }
        

        public IRuntime Runtime { get; private set; }

        private List<IResourceItem> stringResources = new List<IResourceItem>();
        private List<IResourceItem> imageResources = new List<IResourceItem>();

        public IEnumerable<IResourceItem> StringResources { get { return stringResources; } }
        public IEnumerable<IResourceItem> ImageResources { get { return imageResources; } }

        public IPropertySet Properties { get; private set; }

        public bool Enabled { get; set; }

        public IEnumerable<IAddInItemPath> Paths
        {
            get { return paths.Values; }
        }

        private AddIn() 
        {
            Runtime = new Runtime();
        }

        private AddIn(Stream stream)
        {

            Runtime = new Runtime();
            Properties = new PropertySet();

            ParseStream(stream);
        }

        public void Start()
        {
            var nodes = paths.Values.OrderBy(item => item.PathLevel()).ToArray();
            foreach (var item in nodes)
                item.Build(null);
        }

        public void Stop()
        {
            var rs = ServiceLocator.Current.Get<IAddInTree>().FindAllByAddInName(Name).ToArray();
            if (rs != null && rs.Length > 0)
            {
                foreach (var node in rs)
                {
                    var item = ServiceLocator.Current.Get(node.Path);
                    if (item != null)
                    {
                        item.SetProperty("Visible", false);
                        item.SetProperty("Enabled", false);
                    }
                }
            }
        }

        public IAddIn Clone()
        {
            var o = new AddIn();
            o.Roles = Roles;
            o.Runtime = Runtime != null ? Runtime.Clone(): null;
            o.Properties = Properties;
            o.Enabled = Enabled;
            o.HideInManager = HideInManager;

            foreach (var item in stringResources)
                o.stringResources.Add(item.Clone());

            foreach (var item in imageResources)
                o.imageResources.Add(item.Clone());

            foreach (var item in paths)
                o.paths[item.Key] = item.Value.Clone() as IAddInItemPath;

            return o;
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        public static IAddIn Load(string fileName)
        {
            Debug.Assert(!string.IsNullOrEmpty(fileName),"fileName == null");

            logger.Info(String.Format("begin parse addin file \"{0}\"：", Path.GetFileName(fileName)));

            try
            {
                using (var stream = File.OpenRead(fileName))
                    return new AddIn(stream);
            }
            catch (Exception ex)
            {
                ex.RaiseException();
                return null;
            }
            finally
            {
                logger.Info(String.Format("end parse addin file \"{0}\"", Path.GetFileName(fileName)));
            }

            
        }

        public static IAddIn Load(Stream stream)
        {
            Debug.Assert(stream != null, "stream == null");


            try
            {
                return new AddIn(stream);
            }
            catch (Exception ex)
            {
                ex.RaiseException();
                return null;
            }
        }

        public static IAddIn Load(Assembly _Assembly)
        {
            var addInAttr = _Assembly.GetAttribute<AddInAttribute>(false);
            if (addInAttr == null)
                return null;

            var addin = new AddIn();
            addin.Properties = addInAttr.Properties;
            addin.Enabled = addin.Properties.Get<bool>(MbsEnvironment.AddIn.Enabled, true);
            addin.HideInManager = addin.Properties.Get<bool>(MbsEnvironment.AddIn.HideInManager, false);
            addin.Runtime.AddExtensionPoint(new ExtensionPoint(addInAttr.AddInActivator.FullName+"," + _Assembly.GetName().FullName, addInAttr.IsAsyncLoad));

            var stringResourceAttrs = _Assembly.GetAttributes<StringResourceAttribute>(false);
            if (stringResourceAttrs != null && stringResourceAttrs.Length > 0)
            {
                foreach (var item in stringResourceAttrs)
                    addin.stringResources.Add(new ResourceItem { AssemblyFile = _Assembly.CodeBase, BaseResourceName = item.BaseResourceName, ResourceFile = item.ResourceFile });
            }
            var imageResourceAttrs = _Assembly.GetAttributes<ImageResourceAttribute>(false);
            if (imageResourceAttrs != null && imageResourceAttrs.Length > 0)
            {
                foreach (var item in imageResourceAttrs)
                    addin.imageResources.Add(new ResourceItem { AssemblyFile = _Assembly.CodeBase, BaseResourceName = item.BaseResourceName, ResourceFile = item.ResourceFile });
            }
            //var iconResourceAttrs = _Assembly.GetAttributes<IconResourceAttribute>(false);

            var addInItemAttrs = _Assembly.GetAttributes<AddInItemAttribute>(false);//.OrderBy(item=>item.Level).ToArray();
            ParseAddInItem(_Assembly, addin, addInItemAttrs);

            return addin;
        }

        private static void ParseAddInItem(Assembly _Assembly, AddIn addin, AddInItemAttribute[] addInItemAttrs)
        {
            if (addInItemAttrs != null && addInItemAttrs.Length > 0)
            {
                foreach (var item in addInItemAttrs)
                {
                    if (string.IsNullOrEmpty(item.Path))
                        new Exception("Invalid AddItemItem Config in " + _Assembly.CodeBase).RaiseException();

                    var strParentPath = item.ParentPath;
                    item.Properties.Set("Id", item.Id);

                    var addinPath = addin.GetOrCreateAddInItem(strParentPath, null);
                    var addInItem = new AddInItem(addin.Name, strParentPath, item.Properties);

                    addinPath.Items.Add(addInItem);
                }
            }
        }

        internal AddInItemPath GetOrCreateAddInItem(string path, string label)
        {
            IAddInItemPath o = null;
            if (!paths.TryGetValue(path,out o))
            {
                o = new AddInItemPath(Name, path, label);
                var strParentPath = path.LastLeftOf('/');
                IAddInItemPath parentPath = null;

                if (!paths.TryGetValue(strParentPath, out parentPath))
                    paths[path] = o;
                else
                    parentPath.Items.Add(o);
            }
            return o as AddInItemPath;
        }

        public override string ToString()
        {
            return "AddIn:" + Name;
        }
    }

    internal partial class AddIn
    {

        private void ParseStream(Stream stream)
        {

            var doc = new XmlDocument();
            doc.Load(stream);

            if (doc.DocumentElement.Name != "AddIn")
                throw new AddInLoadException("Unknown AddIn");

            if (doc.DocumentElement.Attributes != null)
                foreach (XmlAttribute attr in doc.DocumentElement.Attributes)
                    Properties.Set(attr.LocalName, attr.InnerText);

            Enabled = Properties.Get<bool>(MbsEnvironment.AddIn.Enabled, true);
            HideInManager = Properties.Get<bool>(MbsEnvironment.AddIn.HideInManager, false);

            var roleString = Properties.Get<string>(MbsEnvironment.AddIn.Roles);
            if (!string.IsNullOrEmpty(roleString))
                Roles = roleString.Trim(',').Split(',');

            foreach (XmlNode node in doc.DocumentElement)
            {
                if (node.NodeType != XmlNodeType.Element)
                    return;

                var localName = node.Name;
                switch (localName)
                {
                    case MbsEnvironment.AddIn.Runtime:
                        Runtime = Mbs.Modularity.AddIns.Runtime.Parse(node,Name);
                        break;
                    case MbsEnvironment.AddIn.Resources:
                        ParseResources(node, localName);
                        break;
                    case MbsEnvironment.AddIn.UI:
                        ParseUIElements(node);
                        break;
                    default:
                        throw new AddInLoadException("Unknown root path node:" + localName + " in addin :{0}" + Name);
                }
            }

            doc = null;
        }

       

        private void ParseUIElements(XmlNode xmlNode)
        {
            var node = new XmlConfiguration(xmlNode);

            if (node.Nodes.Count < 1)
                return;
            foreach (var subNode in node.Nodes)
            {
                switch (subNode.Name.ToLower())
                {
                    case "addintree":
                        break;
                }

                ParseAddInTree(subNode);
            }
        } 

        private void ParseAddInTree(INode addinTreeNode)
        {
            if (addinTreeNode.Nodes.Count < 1)
                return;
            foreach (var node in addinTreeNode.Nodes)
            {
                if (node.Name.ToLower() == "path" && node.Attributes.Count > 0)
                {
                    var props = node.Attributes;
                    if (!props.Contains("Id"))
                        throw new AddInLoadException("Found an path node without an 'Id' attribute in addin :{0}" + Name);
                    var name = props.Get<string>("Id");
                    if (string.IsNullOrEmpty(name))
                        throw new AddInLoadException("Found an path node without an 'Id' attribute in addin :{0}" + Name);

                    var path = GetOrCreateAddInItem(name.Trim('/'), props.Get<string>("Label"));

                    AddInItemPath.SetUp(path, node);
                }
            }
        }

        private void ParseResources(XmlNode node, string localName)
        {
            if (node.ChildNodes.Count == 0) return;
            foreach (XmlNode subNode in node.ChildNodes)
            {
                switch (subNode.Name.ToLower())
                {
                    case "stringresources":
                        stringResources.Add( new ResourceItem(new XmlConfiguration(subNode)));
                        break;
                    case "imageresources":
                        imageResources.Add(new ResourceItem(new XmlConfiguration(subNode)));
                        break;
                }
            }
        }
    }
}
